#include "../config.h"
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/uio.h>
#include "fam.h"
#include "gam_protocol.h"
#include "gam_data.h"
#include "gam_fork.h"
#include "gam_error.h"

#define TEST_DEBUG

#define MAX_RETRIES 25

#ifdef GAMIN_DEBUG_API
int debug_reqno = -1;
void *debug_userData = NULL;
#endif
int FAMErrno = 0;

static enum {
  FAM_OK = 0,
  FAM_ARG,  /* Bad arguments */
  FAM_FILE, /* Bad filename */
  FAM_CONNECT,/* Connection failure */
  FAM_AUTH, /* Authentication failure */
  FAM_MEM,  /* Memory allocation */
  FAM_UNIMPLEM/* Unimplemented */
} FAMError;

const char *FamErrlist[] = {
  "Okay",
  "Bad arguments",
  "Bad filename",
  "Connection failure",
  "Authentication failure",
  "Memory allocation failure",
  "Unimplemented function",
  NULL
};

#ifdef GAMIN_DEBUG_API
int FAMDebug( FAMConnection *fc, const char *filename, FAMRequest * fr,
              void *userData );
#endif

#ifdef TEST_DEBUG
static char * gamin_dump_event( FAMEvent *event ) {
  static char res[200];
  const char *type;
  if( event == NULL ) {
    return( "NULL event !" );
  }
  switch( event->code ) {
    case FAMChanged:
      type = "Changed";
      break;
    case FAMDeleted:
      type = "Deleted";
      break;
    case FAMStartExecuting:
      type = "StartExecuting";
      break;
    case FAMStopExecuting:
      type = "StopExecuting";
      break;
    case FAMCreated:
      type = "Created";
      break;
    case FAMMoved:
      type = "Moved";
      break;
    case FAMAcknowledge:
      type = "Acknowledge";
      break;
    case FAMExists:
      type = "Exists";
      break;
    case FAMEndExist:
      type = "EndExist";
      break;
    default:
      type = "Unknown";
      break;
  }
  snprintf( res, 199, "%s : %s", type, &event->filename[0] );
  return( res );
}
#endif

void gam_show_debug( void );

void gam_show_debug( void ) {
}

void gam_got_signal( void );

void gam_got_signal( void ) {
}

static char user_name[100] = "";

static const char * gamin_get_user_name( void ) {
  struct passwd *pw;
  if( user_name[0] != 0 ) {
    return ( user_name );
  }
  pw = getpwuid( getuid() );
  if( pw != NULL ) {
    strncpy( user_name, pw->pw_name, 99 );
    user_name[99] = 0;
  }
  return( user_name );
}

static char * gamin_get_socket_path( void ) {
  const char *fam_client_id;
  const char *user;
  char *ret;
  char path[MAXPATHLEN + 1];
  fam_client_id = getenv( "GAM_CLIENT_ID" );
  if( fam_client_id == NULL ) {
    GAM_DEBUG( DEBUG_INFO, "Error getting GAM_CLIENT_ID\n" );
    fam_client_id = "";
  }
  user = gamin_get_user_name();
  if( user == NULL ) {
    gam_error( DEBUG_INFO, "Error getting user informations" );
    return ( NULL );
  }
  snprintf( path, MAXPATHLEN, "/tmp/fam-%s-%s", user, fam_client_id );
  path[MAXPATHLEN] = 0;
  ret = strdup( path );
  return ( ret );
}

#ifndef HAVE_ABSTRACT_SOCKETS

static char * gamin_get_socket_dir( void ) {
  const char *user;
  char *ret;
  char path[MAXPATHLEN + 1];
  user = gamin_get_user_name();
  if( user == NULL ) {
    gam_error( DEBUG_INFO, "Error getting user informations" );
    return ( NULL );
  }
  snprintf( path, MAXPATHLEN, "/tmp/fam-%s", user );
  path[MAXPATHLEN] = 0;
  ret = strdup( path );
  return ( ret );
}

static int gamin_check_secure_dir( void ) {
  char *dir;
  struct stat st;
  int ret;
  dir = gamin_get_socket_dir();
  if( dir == NULL ) {
    gam_error( DEBUG_INFO, "Failed to get path to socket directory\n" );
    return( 0 );
  }
  ret = stat( dir, &st );
  if( ret < 0 ) {
    free( dir );
    return( 0 );
  }
  if( st.st_uid != getuid() ) {
    gam_error( DEBUG_INFO,
               "Socket directory %s has different owner\n",
               dir );
    goto unsafe;
  }
  if( !S_ISDIR( st.st_mode ) ) {
    gam_error( DEBUG_INFO, "Socket path %s is not a directory\n",
               dir );
    goto unsafe;
  }
  if( st.st_mode & ( S_IRWXG | S_IRWXO ) ) {
    gam_error( DEBUG_INFO,
               "Socket directory %s has wrong permissions\n",
               dir );
    goto unsafe;
  }
  if( ( ( st.st_mode & ( S_IRWXU ) ) != S_IRWXU ) ) {
    gam_error( DEBUG_INFO,
               "Socket directory %s has wrong permissions\n",
               dir );
    goto unsafe;
  }
  GAM_DEBUG( DEBUG_INFO, "Reusing socket directory %s\n", dir );
  free( dir );
  return( 1 );
unsafe:
  ret = rmdir( dir );
  if( ret < 0 ) {
    ret = unlink( dir );
    if( ret < 0 ) {
      gam_error( DEBUG_INFO, "Failed to remove unsafe path %s\n",
                 dir );
      free( dir );
      return( -1 );
    }
  }
  GAM_DEBUG( DEBUG_INFO, "Removed %s\n", dir );
  free( dir );
  return( 0 );
}

static int gamin_check_secure_path( const char *path ) {
  struct stat st;
  int ret;
  ret = gamin_check_secure_dir();
  if( ret <= 0 ) {
    return( ret );
  }
  ret = stat( path, &st );
  if( ret < 0 ) {
    return( 0 );
  }
  if( st.st_uid != getuid() ) {
    gam_error( DEBUG_INFO,
               "Socket %s has different owner\n",
               path );
    goto cleanup;
  }
  #ifdef S_ISSOCK
  if( !S_ISSOCK( st.st_mode ) ) {
    gam_error( DEBUG_INFO, "Socket path %s is not a socket\n",
               path );
    goto cleanup;
  }
  #endif
  if( st.st_mode & ( S_IRWXG | S_IRWXO ) ) {
    gam_error( DEBUG_INFO,
               "Socket %s has wrong permissions\n",
               path );
    goto cleanup;
  }
  return( 1 );
cleanup:
  ret = unlink( path );
  if( ret < 0 ) {
    gam_error( DEBUG_INFO, "Failed to remove %s\n", path );
    return( -1 );
  }
  return( 0 );
}
#endif /* ! HAVE_ABSTRACT_SOCKETS */

static int gamin_connect_unix_socket( const char *path ) {
  int fd;
  struct sockaddr_un addr;
  int retries = 0;
retry_start:
  fd = socket( PF_UNIX, SOCK_STREAM, 0 );
  if( fd < 0 ) {
    gam_error( DEBUG_INFO, "无法创建unix套接字\n" );
    return ( -1 );
  }
  memset( &addr, 0, sizeof( addr ) );
  addr.sun_family = AF_UNIX;
  #ifdef HAVE_ABSTRACT_SOCKETS
  addr.sun_path[0] = '\0';
  strncpy( &addr.sun_path[1], path, ( sizeof( addr ) - 4 ) - 2 );
  #else
  if( gamin_check_secure_path( path ) < 0 ) {
    return ( -1 );
  }
  strncpy( &addr.sun_path[0], path, ( sizeof( addr ) - 4 ) - 1 );
  #endif
  if( connect( fd, ( struct sockaddr * ) &addr, sizeof( addr ) ) < 0 ) {
    if( retries == 0 ) {
      const char *fam_client_id = getenv( "GAM_CLIENT_ID" );
      if( fam_client_id == NULL ) {
        fam_client_id = "";
      }
      close( fd );
      gamin_fork_server( fam_client_id );
      retries++;
      goto retry_start;
    }
    if( retries < MAX_RETRIES ) {
      close( fd );
      usleep( 50000 );
      retries++;
      goto retry_start;
    }
    gam_error( DEBUG_INFO, "未能连接到套接字 %s\n", path );
    close( fd );
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "连接到套接字 %s : %d\n", path, fd );
  return ( fd );
}

static int gamin_write_credential_byte( int fd ) {
  char data[2] = { 0, 0 };
  int written;
  #if defined(HAVE_CMSGCRED) && !defined(LOCAL_CREDS)
  struct {
    struct cmsghdr hdr;
    struct cmsgcred cred;
  } cmsg;
  struct iovec iov;
  struct msghdr msg;
  iov.iov_base = &data[0];
  iov.iov_len = 1;
  memset( &msg, 0, sizeof( msg ) );
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = &cmsg;
  msg.msg_controllen = sizeof( cmsg );
  memset( &cmsg, 0, sizeof( cmsg ) );
  cmsg.hdr.cmsg_len = sizeof( cmsg );
  cmsg.hdr.cmsg_level = SOL_SOCKET;
  cmsg.hdr.cmsg_type = SCM_CREDS;
  #endif
retry:
  #if defined(HAVE_CMSGCRED) && !defined(LOCAL_CREDS)
  written = sendmsg( fd, &msg, 0 );
  #else
  written = write( fd, &data[0], 1 );
  #endif
  if( written < 0 ) {
    if( errno == EINTR ) {
      goto retry;
    }
    gam_error( DEBUG_INFO,
               "Failed to write credential bytes to socket %d\n", fd );
    return ( -1 );
  }
  if( written != 1 ) {
    gam_error( DEBUG_INFO, "Wrote %d credential bytes to socket %d\n",
               written, fd );
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "Wrote credential bytes to socket %d\n", fd );
  return ( 0 );
}

static int gamin_data_available( int fd ) {
  fd_set read_set;
  struct timeval tv;
  int avail;
  if( fd < 0 ) {
    GAM_DEBUG( DEBUG_INFO, "gamin_data_available wrong fd %d\n", fd );
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "Checking data available on %d\n", fd );
retry:
  FD_ZERO( &read_set );
  FD_SET( fd, &read_set );
  tv.tv_sec = 0;
  tv.tv_usec = 0;
  avail = select( fd + 1, &read_set, NULL, NULL, &tv );
  if( avail < 0 ) {
    if( errno == EINTR ) {
      goto retry;
    }
    gam_error( DEBUG_INFO,
               "Failed to check data availability on socket %d\n", fd );
    return ( -1 );
  }
  if( avail == 0 ) {
    return ( 0 );
  }
  return ( 1 );
}

static int gamin_write_byte( int fd, const char *data, size_t len ) {
  int written;
  int remaining;
  remaining = len;
  do {
    written = write( fd, data, remaining );
    if( written < 0 ) {
      if( errno == EINTR ) {
        continue;
      }
      GAM_DEBUG( DEBUG_INFO,
                 "%s: Failed to write bytes to socket %d: %s\n",
                 __FUNCTION__, fd, strerror( errno ) );
      return -1;
    }
    data += written;
    remaining -= written;
  } while( remaining > 0 );
  GAM_DEBUG( DEBUG_INFO, "Wrote %d bytes to socket %d\n", written, fd );
  return ( 0 );
}

static int gamin_send_request( GAMReqType type, int fd, const char *filename,
                               FAMRequest * fr, void *userData, GAMDataPtr data,
                               int has_reqnum ) {
  int reqnum;
  size_t len, tlen;
  GAMPacket req;
  int ret;
  #ifdef GAMIN_DEBUG_API
  if( type == GAM_REQ_DEBUG ) {
    len = strlen( filename );
    if( len > MAXPATHLEN ) {
      FAMErrno = FAM_FILE;
      return ( -1 );
    }
    reqnum = gamin_data_get_reqnum( data, filename, ( int ) type, userData );
    if( reqnum < 0 ) {
      FAMErrno = FAM_ARG;
      return ( -1 );
    }
    reqnum = fr->reqnum;
  } else
  #endif
    if( filename == NULL ) {
      len = 0;
      reqnum = fr->reqnum;
    } else if( has_reqnum == 0 ) {
      len = strlen( filename );
      if( len > MAXPATHLEN ) {
        FAMErrno = FAM_FILE;
        return ( -1 );
      }
      reqnum = gamin_data_get_reqnum( data, filename, ( int ) type, userData );
      if( reqnum < 0 ) {
        FAMErrno = FAM_ARG;
        return ( -1 );
      }
      fr->reqnum = reqnum;
    } else {
      len = strlen( filename );
      if( len > MAXPATHLEN ) {
        FAMErrno = FAM_FILE;
        return ( -1 );
      }
      reqnum = gamin_data_get_request( data, filename, ( int ) type, userData,
                                       fr->reqnum );
      if( reqnum < 0 ) {
        FAMErrno = FAM_MEM;
        return ( -1 );
      }
    }
  tlen = GAM_PACKET_HEADER_LEN + len;
  req.len = ( unsigned short ) tlen;
  req.version = GAM_PROTO_VERSION;
  req.seq = reqnum;
  req.type = ( unsigned short ) type;
  if( ( type == GAM_REQ_DIR ) && ( gamin_data_get_exists( data ) == 0 ) ) {
    req.type |= GAM_OPT_NOEXISTS;
  }
  req.pathlen = len;
  if( len > 0 ) {
    memcpy( &req.path[0], filename, len );
  }
  ret = gamin_write_byte( fd, ( const char * ) &req, tlen );
  GAM_DEBUG( DEBUG_INFO, "gamin_send_request %d for socket %d\n", reqnum,
             fd );
  if( ret < 0 ) {
    FAMErrno = FAM_CONNECT;
  }
  return ( ret );
}

static int gamin_check_cred( GAMDataPtr conn, int fd ) {
  struct msghdr msg;
  struct iovec iov;
  char buf;
  pid_t c_pid;
  uid_t c_uid, s_uid;
  gid_t c_gid;
  #ifdef HAVE_CMSGCRED
  struct {
    struct cmsghdr hdr;
    struct cmsgcred cred;
  } cmsg;
  #endif
  s_uid = getuid();
  #if defined(LOCAL_CREDS) && defined(HAVE_CMSGCRED)
  {
    int on = 1;
    if( setsockopt( fd, 0, LOCAL_CREDS, &on, sizeof( on ) ) < 0 ) {
      gam_error( DEBUG_INFO, "Unable to set LOCAL_CREDS socket option\n" );
      return( -1 );
    }
  }
  #endif
  iov.iov_base = &buf;
  iov.iov_len = 1;
  memset( &msg, 0, sizeof( msg ) );
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  #ifdef HAVE_CMSGCRED
  memset( &cmsg, 0, sizeof( cmsg ) );
  msg.msg_control = &cmsg;
  msg.msg_controllen = sizeof( cmsg );
  #endif
retry:
  if( recvmsg( fd, &msg, 0 ) < 0 ) {
    if( errno == EINTR ) {
      goto retry;
    }
    GAM_DEBUG( DEBUG_INFO, "Failed to read credentials byte on %d\n", fd );
    goto failed;
  }
  if( buf != '\0' ) {
    GAM_DEBUG( DEBUG_INFO, "Credentials byte was not nul on %d\n", fd );
    goto failed;
  }
  #ifdef HAVE_CMSGCRED
  if( cmsg.hdr.cmsg_len < sizeof( cmsg ) || cmsg.hdr.cmsg_type != SCM_CREDS ) {
    GAM_DEBUG( DEBUG_INFO,
               "Message from recvmsg() was not SCM_CREDS\n" );
    goto failed;
  }
  #endif
  GAM_DEBUG( DEBUG_INFO, "read credentials byte\n" );
  {
    #ifdef SO_PEERCRED
    struct ucred cr;
    socklen_t cr_len = sizeof( cr );
    if( getsockopt( fd, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len ) ==
        0 && cr_len == sizeof( cr ) ) {
      c_pid = cr.pid;
      c_uid = cr.uid;
      c_gid = cr.gid;
    } else {
      GAM_DEBUG( DEBUG_INFO,
                 "Failed to getsockopt() credentials on %d, returned len %d/%d\n",
                 fd, cr_len, ( int ) sizeof( cr ) );
      goto failed;
    }
    #elif defined(HAVE_CMSGCRED)
    c_pid = cmsg.cred.cmcred_pid;
    c_uid = cmsg.cred.cmcred_euid;
    c_gid = cmsg.cred.cmcred_groups[0];
    #else /* !SO_PEERCRED && !HAVE_CMSGCRED */
    GAM_DEBUG( DEBUG_INFO,
               "Socket credentials not supported on this OS\n" );
    goto failed;
    #endif
  }
  if( s_uid != c_uid ) {
    GAM_DEBUG( DEBUG_INFO,
               "Credentials check failed: s_uid %d, c_uid %d\n",
               ( int ) s_uid, ( int ) c_uid );
    goto failed;
  }
  GAM_DEBUG( DEBUG_INFO,
             "Credentials: s_uid %d, c_uid %d, c_gid %d, c_pid %d\n",
             ( int ) s_uid, ( int ) c_uid, ( int ) c_gid, ( int ) c_pid );
  gamin_data_done_auth( conn );
  return( 0 );
failed:
  return ( -1 );
}

static int gamin_read_data( GAMDataPtr conn, int fd, int block ) {
  int ret;
  char *data;
  int size;
  ret = gamin_data_need_auth( conn );
  if( ret == 1 ) {
    GAM_DEBUG( DEBUG_INFO, "Client need auth %d\n", fd );
    if( gamin_check_cred( conn, fd ) < 0 ) {
      return ( -1 );
    }
    if( !block ) {
      ret = gamin_data_available( fd );
      if( ret < 0 ) {
        return( -1 );
      }
      if( ret == 0 ) {
        return( 0 );
      }
    }
  } else if( ret != 0 ) {
    goto error;
  }
  ret = gamin_data_get_data( conn, &data, &size );
  if( ret < 0 ) {
    GAM_DEBUG( DEBUG_INFO, "Failed getting connection data\n" );
    goto error;
  }
retry:
  ret = read( fd, ( char * ) data, size );
  if( ret < 0 ) {
    if( errno == EINTR ) {
      GAM_DEBUG( DEBUG_INFO, "client read() call interrupted\n" );
      goto retry;
    }
    gam_error( DEBUG_INFO, "failed to read() from server connection\n" );
    goto error;
  }
  if( ret == 0 ) {
    gam_error( DEBUG_INFO, "end from FAM server connection\n" );
    goto error;
  }
  GAM_DEBUG( DEBUG_INFO, "read %d bytes from server\n", ret );
  if( gamin_data_conn_data( conn, ret ) < 0 ) {
    gam_error( DEBUG_INFO, "Failed to process %d bytes from server\n",
               ret );
    goto error;
  }
  return ( 0 );
error:
  return( -1 );
}

static int gamin_resend_request( int fd, GAMReqType type, const char *filename,
                                 int reqnum ) {
  size_t len, tlen;
  GAMPacket req;
  int ret;
  if( ( filename == NULL ) || ( fd < 0 ) ) {
    return( -1 );
  }
  len = strlen( filename );
  tlen = GAM_PACKET_HEADER_LEN + len;
  req.len = ( unsigned short ) tlen;
  req.version = GAM_PROTO_VERSION;
  req.seq = reqnum;
  req.type = ( unsigned short )( type | GAM_OPT_NOEXISTS );
  req.pathlen = len;
  if( len > 0 ) {
    memcpy( &req.path[0], filename, len );
  }
  ret = gamin_write_byte( fd, ( const char * ) &req, tlen );
  GAM_DEBUG( DEBUG_INFO, "gamin_resend_request %d for socket %d\n", reqnum,
             fd );
  return ( ret );
}

static int gamin_try_reconnect( GAMDataPtr conn, int fd ) {
  int newfd, i, ret, nb_req;
  GAMReqDataPtr *reqs;
  char *socket_name;
  if( ( conn == NULL ) || ( fd < 0 ) ) {
    return( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "Trying to reconnect to server on %d\n", fd );
  socket_name = gamin_get_socket_path();
  if( socket_name == NULL ) {
    return ( -1 );
  }
  newfd = gamin_connect_unix_socket( socket_name );
  free( socket_name );
  if( newfd < 0 ) {
    return ( -1 );
  }
  ret = gamin_write_credential_byte( newfd );
  if( ret != 0 ) {
    close( newfd );
    return ( -1 );
  }
  ret = dup2( newfd, fd );
  close( newfd );
  if( ret < 0 ) {
    gam_error( DEBUG_INFO,
               "Failed to reuse descriptor %d on reconnect\n",
               fd );
    return ( -1 );
  }
  nb_req = gamin_data_reset( conn, &reqs );
  if( reqs != NULL ) {
    for( i = 0; i < nb_req; i++ ) {
      gamin_resend_request( fd, reqs[i]->type, reqs[i]->filename,
                            reqs[i]->reqno );
    }
  }
  return( 0 );
}

int FAMOpen( FAMConnection * fc ) {
  char *socket_name;
  int fd, ret;
  gam_error_init();
  GAM_DEBUG( DEBUG_INFO, "FAMOpen()\n" );
  if( fc == NULL ) {
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  socket_name = gamin_get_socket_path();
  if( socket_name == NULL ) {
    FAMErrno = FAM_CONNECT;
    return ( -1 );
  }
  fd = gamin_connect_unix_socket( socket_name );
  free( socket_name );
  if( fd < 0 ) {
    FAMErrno = FAM_CONNECT;
    return ( -1 );
  }
  ret = gamin_write_credential_byte( fd );
  if( ret != 0 ) {
    FAMErrno = FAM_CONNECT;
    close( fd );
    return ( -1 );
  }
  fc->fd = fd;
  fc->client = ( void * ) gamin_data_new();
  if( fc->client == NULL ) {
    FAMErrno = FAM_MEM;
    close( fd );
    return ( -1 );
  }
  return ( 0 );
}

int FAMOpen2( FAMConnection * fc, const char *appName ) {
  int ret;
  gam_error_init();
  GAM_DEBUG( DEBUG_INFO, "FAMOpen2()\n" );
  ret = FAMOpen( fc );
  return ( ret );
}

int FAMClose( FAMConnection * fc ) {
  int ret;
  if( fc == NULL ) {
    FAMErrno = FAM_ARG;
    GAM_DEBUG( DEBUG_INFO, "FAMClose() arg error\n" );
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "FAMClose()\n" );
  gamin_data_lock( fc->client );
  ret = close( fc->fd );
  fc->fd = -1;
  gamin_data_free( fc->client );
  return ( ret );
}

int FAMMonitorDirectory( FAMConnection * fc, const char *filename,
                         FAMRequest * fr, void *userData ) {
  int retval;
  if( ( fc == NULL ) || ( filename == NULL ) || ( fr == NULL ) ) {
    GAM_DEBUG( DEBUG_INFO, "FAMMonitorDirectory() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "FAMMonitorDirectory(%s)\n", filename );
  if( ( filename[0] != '/' ) || ( strlen( filename ) >= MAXPATHLEN ) ) {
    FAMErrno = FAM_FILE;
    return ( -1 );
  }
  if( ( fc->fd < 0 ) || ( fc->client == NULL ) ) {
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  gamin_data_lock( fc->client );
  retval = ( gamin_send_request( GAM_REQ_DIR, fc->fd, filename,
                                 fr, userData, fc->client, 0 ) );
  gamin_data_unlock( fc->client );
  return retval;
}

int FAMMonitorDirectory2( FAMConnection * fc, const char *filename,
                          FAMRequest * fr ) {
  int retval;
  if( ( fc == NULL ) || ( filename == NULL ) || ( fr == NULL ) ) {
    GAM_DEBUG( DEBUG_INFO, "FAMMonitorDirectory2() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "FAMMonitorDirectory2(%s, %d)\n",
             filename, fr->reqnum );
  if( ( filename[0] != '/' ) || ( strlen( filename ) >= MAXPATHLEN ) ) {
    FAMErrno = FAM_FILE;
    return ( -1 );
  }
  if( ( fc->fd < 0 ) || ( fc->client == NULL ) ) {
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  gamin_data_lock( fc->client );
  retval = ( gamin_send_request( GAM_REQ_DIR, fc->fd, filename,
                                 fr, NULL, fc->client, 1 ) );
  gamin_data_unlock( fc->client );
  return retval;
}

int FAMMonitorFile( FAMConnection * fc, const char *filename,
                    FAMRequest * fr, void *userData ) {
  int retval;
  if( ( fc == NULL ) || ( filename == NULL ) || ( fr == NULL ) ) {
    GAM_DEBUG( DEBUG_INFO, "FAMMonitorFile() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "FAMMonitorFile(%s)\n", filename );
  if( ( filename[0] != '/' ) || ( strlen( filename ) >= MAXPATHLEN ) ) {
    FAMErrno = FAM_FILE;
    return ( -1 );
  }
  if( ( fc->fd < 0 ) || ( fc->client == NULL ) ) {
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  gamin_data_lock( fc->client );
  retval = ( gamin_send_request( GAM_REQ_FILE, fc->fd, filename,
                                 fr, userData, fc->client, 0 ) );
  gamin_data_unlock( fc->client );
  return retval;
}

int FAMMonitorFile2( FAMConnection * fc, const char *filename, FAMRequest * fr ) {
  int retval;
  if( ( fc == NULL ) || ( filename == NULL ) || ( fr == NULL ) ) {
    GAM_DEBUG( DEBUG_INFO, "FAMMonitorFile2() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "FAMMonitorFile2(%s, %d)\n", filename, fr->reqnum );
  if( ( filename[0] != '/' ) || ( strlen( filename ) >= MAXPATHLEN ) ) {
    FAMErrno = FAM_FILE;
    return ( -1 );
  }
  if( ( fc->fd < 0 ) || ( fc->client == NULL ) ) {
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  gamin_data_lock( fc->client );
  retval = ( gamin_send_request( GAM_REQ_FILE, fc->fd, filename,
                                 fr, NULL, fc->client, 1 ) );
  gamin_data_unlock( fc->client );
  return retval;
}

int FAMMonitorCollection( FAMConnection * fc, const char *filename,
                          FAMRequest * fr, void *userData, int depth,
                          const char *mask ) {
  if( filename == NULL ) {
    filename = "NULL";
  }
  if( mask == NULL ) {
    mask = "NULL";
  }
  gam_error( DEBUG_INFO,
             "Unsupported call filename %s, depth %d, mask %s\n",
             filename, depth, mask );
  FAMErrno = FAM_UNIMPLEM;
  return ( -1 );
}

int FAMNextEvent( FAMConnection * fc, FAMEvent * fe ) {
  int ret;
  int fd;
  GAMDataPtr conn;
  if( ( fc == NULL ) || ( fe == NULL ) ) {
    GAM_DEBUG( DEBUG_INFO, "FAMNextEvent() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  conn = fc->client;
  if( conn == NULL ) {
    GAM_DEBUG( DEBUG_INFO, "FAMNextEvent() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "FAMNextEvent(fd = %d)\n", fc->fd );
  fd = fc->fd;
  if( fd < 0 ) {
    return ( -1 );
  }
  gamin_data_lock( conn );
  if( !gamin_data_event_ready( conn ) ) {
    if( gamin_read_data( conn, fc->fd, 1 ) < 0 ) {
      gamin_try_reconnect( conn, fc->fd );
      FAMErrno = FAM_CONNECT;
      return ( -1 );
    }
  }
  ret = gamin_data_read_event( conn, fe );
  gamin_data_unlock( conn );
  if( ret < 0 ) {
    FAMErrno = FAM_CONNECT;
    return ( ret );
  }
  fe->fc = fc;
  #ifdef TEST_DEBUG
  GAM_DEBUG( DEBUG_INFO, "FAMNextEvent : %s\n", gamin_dump_event( fe ) );
  #endif
  return ( 1 );
}

int FAMPending( FAMConnection * fc ) {
  int ret;
  GAMDataPtr conn;
  if( fc == NULL ) {
    GAM_DEBUG( DEBUG_INFO, "FAMPending() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  conn = fc->client;
  if( conn == NULL ) {
    GAM_DEBUG( DEBUG_INFO, "FAMPending() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "FAMPending(fd = %d)\n", fc->fd );
  gamin_data_lock( conn );
  if( gamin_data_event_ready( conn ) ) {
    gamin_data_unlock( conn );
    return ( 1 );
  }
  ret = gamin_data_available( fc->fd );
  if( ret < 0 ) {
    return ( -1 );
  }
  if( ret > 0 ) {
    if( gamin_read_data( conn, fc->fd, 0 ) < 0 ) {
      gamin_try_reconnect( conn, fc->fd );
    }
  }
  ret = ( gamin_data_event_ready( conn ) );
  gamin_data_unlock( conn );
  return ret;
}

int FAMCancelMonitor( FAMConnection * fc, const FAMRequest * fr ) {
  int ret;
  GAMDataPtr conn;
  if( ( fc == NULL ) || ( fr == NULL ) ) {
    GAM_DEBUG( DEBUG_INFO, "FAMCancelMonitor() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  if( ( fc->fd < 0 ) || ( fc->client == NULL ) ) {
    GAM_DEBUG( DEBUG_INFO, "FAMCancelMonitor() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "FAMCancelMonitor(%d)\n", fr->reqnum );
  conn = fc->client;
  gamin_data_lock( conn );
  ret = gamin_data_cancel( conn, fr->reqnum );
  if( ret < 0 ) {
    FAMErrno = FAM_ARG;
    gamin_data_unlock( conn );
    return ( -1 );
  }
  ret = gamin_send_request( GAM_REQ_CANCEL, fc->fd, NULL,
                            ( FAMRequest * ) fr, NULL, fc->client, 0 );
  gamin_data_unlock( conn );
  if( ret != 0 ) {
    FAMErrno = FAM_CONNECT;
  }
  return ( ret );
}

int FAMSuspendMonitor( FAMConnection *fc, const FAMRequest *fr ) {
  gam_error( DEBUG_INFO,
             "Unsupported call to FAMSuspendMonitor()\n" );
  FAMErrno = FAM_UNIMPLEM;
  return ( -1 );
}

int FAMResumeMonitor( FAMConnection *fc, const FAMRequest *fr ) {
  gam_error( DEBUG_INFO,
             "Unsupported call to FAMResumeMonitor()\n" );
  FAMErrno = FAM_UNIMPLEM;
  return ( -1 );
}

int FAMNoExists( FAMConnection *fc ) {
  int ret;
  GAMDataPtr conn;
  if( fc == NULL ) {
    GAM_DEBUG( DEBUG_INFO, "FAMNoExists() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  conn = fc->client;
  gamin_data_lock( conn );
  ret = gamin_data_no_exists( conn );
  gamin_data_unlock( conn );
  if( ret < 0 ) {
    GAM_DEBUG( DEBUG_INFO, "FAMNoExists() arg error\n" );
    FAMErrno = FAM_ARG;
    return( -1 );
  }
  return( 0 );
}

#ifdef GAMIN_DEBUG_API
int FAMDebug( FAMConnection *fc, const char *filename, FAMRequest * fr,
              void *userData ) {
  int ret;
  if( ( fc == NULL ) || ( filename == NULL ) || ( fr == NULL ) ) {
    GAM_DEBUG( DEBUG_INFO, "FAMDebug() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  if( strlen( filename ) >= MAXPATHLEN ) {
    FAMErrno = FAM_FILE;
    return ( -1 );
  }
  if( ( fc->fd < 0 ) || ( fc->client == NULL ) ) {
    GAM_DEBUG( DEBUG_INFO, "FAMDebug() arg error\n" );
    FAMErrno = FAM_ARG;
    return ( -1 );
  }
  GAM_DEBUG( DEBUG_INFO, "FAMDebug(%s)\n", filename );
  gamin_data_lock( fc->client );
  ret = gamin_send_request( GAM_REQ_DEBUG, fc->fd, filename,
                            fr, userData, fc->client, 0 );
  gamin_data_unlock( fc->client );
  if( debug_reqno == -1 ) {
    debug_reqno = fr->reqnum;
    debug_userData = userData;
  }
  return( ret );
}

int FAMDebugLevel( FAMConnection *fc, int level ) {
  return( 1 );
}
#endif
